/**
 * Global Services, PTC
 *
 * Copyright (c) 1985~2012 Parametric Technology Corporation (PTC). All Rights
 * Reserved.
 *
 * This software is the confidential and proprietary information of PTC
 * and is subject to the terms of a software license agreement. You shall
 * not disclose such confidential information and shall use it only in accordance
 * with the terms of the license agreement.
 *
 * Created on 11-12-14 ����10:07,
 * Created by Dong Qi
 */

package ext.acae2.util;

import org.apache.log4j.Logger;
import wt.log4j.LogR;
import wt.method.RemoteMethodServer;
import wt.util.WTException;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.rmi.RemoteException;

/**
 * The class <code>ext.ideal.util.ReflectionUtil</code>
 * provides several operations for java reflection.
 *
 * @author Qi Dong
 */
public class ReflectionUtil {
    private static final SecurityManager DEFAULT_SECURITY_MANAGER = System.getSecurityManager();

    private static Logger logger = LogR.getLogger(ReflectionUtil.class.getName());

    /**
     * Create new instance by no-arg constructor
     *
     * @param className class name
     * @return new instance
     * @throws WTException Windchill exception
     */
    public static Object newInstanceByNoArgConstructor(String className) throws WTException {
        Object newInstance;
        try {
            Class cls = Class.forName(className);
            newInstance = cls.newInstance();
        } catch (ClassNotFoundException e) {
            logger.error(e);
            throw new WTException(e);
        } catch (IllegalAccessException e) {
            logger.error(e);
            throw new WTException(e);
        } catch (InstantiationException e) {
            logger.error(e);
            throw new WTException(e);
        }
        return newInstance;
    }

    /**
     * Construct a new instance forcely
     *
     * @param cls       class instance
     * @param argTypes  constructor argument types
     * @param argValues constructor argument values
     * @return object instance
     * @throws WTException Winchill exception
     * @see java.lang.reflect.Constructor
     * @see java.lang.reflect.AccessibleObject
     */
    public static Object newInstanceForcely(Class cls, Class[] argTypes, Object[] argValues) throws WTException {
        Object newInstance;
        try {
            Constructor constructor = cls.getDeclaredConstructor(argTypes);
            System.setSecurityManager(null);
            constructor.setAccessible(true);
            System.setSecurityManager(DEFAULT_SECURITY_MANAGER);
            newInstance = constructor.newInstance(argValues);
        } catch (NoSuchMethodException e) {
            logger.error(e);
            throw new WTException(e);
        } catch (InvocationTargetException e) {
            logger.error(e);
            throw new WTException(e);
        } catch (IllegalAccessException e) {
            logger.error(e);
            throw new WTException(e);
        } catch (InstantiationException e) {
            logger.error(e);
            throw new WTException(e);
        }
        return newInstance;
    }

    /**
     * Invoke a method forcely.
     *
     * @param className  class name
     * @param methodName method name
     * @param obj        object to be invoked on
     * @param argTypes   method argument types
     * @param argValues  argument values
     * @return method result, can be null while no return value
     * @see java.lang.reflect.AccessibleObject
     * @see java.lang.reflect.Method
     */
    public static Object invokeMethodForcely(String className, String methodName, Object obj, Class[] argTypes, Object[] argValues) {
        Object ret = null;
        try {
            Class cls = Class.forName(className);
            ret = invokeMethodForcely(cls, methodName, obj, argTypes, argValues);
        } catch (ClassNotFoundException e) {
            logger.error(e);
        }
        return ret;
    }

    /**
     * Invoke a method forcely.
     *
     * @param clazz      the class instance
     * @param methodName method name
     * @param obj        object to be invoked on
     * @param argTypes   method argument types
     * @param argValues  argument values
     * @return method result, can be null while no return value
     * @see java.lang.reflect.AccessibleObject
     * @see java.lang.reflect.Method
     */
    public static Object invokeMethodForcely(Class clazz, String methodName, Object obj, Class[] argTypes, Object[] argValues) {
        Object ret = null;
        try {
            Method method = clazz.getDeclaredMethod(methodName, argTypes);
            System.setSecurityManager(null);
            method.setAccessible(true);
            System.setSecurityManager(DEFAULT_SECURITY_MANAGER);
            if (void.class.equals(method.getReturnType())) {
                method.invoke(obj, argValues);
            } else {
                ret = method.invoke(obj, argValues);
            }
        } catch (NoSuchMethodException e) {
            logger.error(e);
        } catch (InvocationTargetException e) {
            logger.error(e);
        } catch (IllegalAccessException e) {
            logger.error(e);
        }
        return ret;
    }

    /**
     * Invoke method on method server, if no return value for the invoking method, this method always return <code>null</code>
     *
     * @param methodName method name
     * @param cls        class
     * @param instance   instance
     * @param argTypes   arrays of arguments' types
     * @param argValues  arrays of arguments' values
     * @return method return value
     * @throws WTException Windchill exception
     */
    public static Object invokeOnMethodServer(String methodName, Class cls,
                                              Object instance, Class[] argTypes, Object[] argValues) throws WTException {
        Object retValue = null;
        RemoteMethodServer methodServer = RemoteMethodServer.getDefault();
        if (hasReturnValue(methodName, cls, argTypes)) {
            try {
                retValue = methodServer.invoke(methodName, cls.getName(), instance, argTypes, argValues);
            } catch (RemoteException e) {
                logger.error(e);
                throw new WTException(e);
            } catch (InvocationTargetException e) {
                logger.error(e);
                throw new WTException(e);
            }
        } else {
            try {
                methodServer.invoke(methodName, cls.getName(), instance, argTypes, argValues);
            } catch (RemoteException e) {
                logger.error(e);
                throw new WTException(e);
            } catch (InvocationTargetException e) {
                logger.error(e);
                throw new WTException(e);
            }
        }
        return retValue;
    }

    /**
     * Access field forcely
     *
     * @param className class name
     * @param obj       object
     * @param fieldName field name
     * @return field
     * @throws wt.util.WTException Windchill exception
     */
    public static Object accessFieldForcely(String className, Object obj, String fieldName) throws WTException {
        Object fieldValue;
        try {
            Class cls = Class.forName(className);
            fieldValue = accessFieldForcely(cls, obj, fieldName);
        } catch (ClassNotFoundException e) {
            logger.error(e);
            throw new WTException(e);
        }
        return fieldValue;
    }

    /**
     * Access field forcely
     *
     * @param clazz     the class instance
     * @param obj       object
     * @param fieldName field name
     * @return field
     * @throws wt.util.WTException Windchill exception
     */
    public static Object accessFieldForcely(Class clazz, Object obj, String fieldName) throws WTException {
        Object fieldValue;
        try {
            Field field = getField(clazz, fieldName);
            System.setSecurityManager(null);
            field.setAccessible(true);
            System.setSecurityManager(DEFAULT_SECURITY_MANAGER);
            fieldValue = field.get(obj);
        } catch (IllegalAccessException e) {
            logger.error(e);
            throw new WTException(e);
        }
        return fieldValue;
    }

    /**
     * Set field value forcely
     *
     * @param className class name
     * @param obj       object
     * @param fieldName field name
     * @param value     field value
     * @throws WTException Windchill exception
     */
    public static void setFieldValueForcely(String className, Object obj, String fieldName, Object value) throws WTException {
        try {
            Class cls = Class.forName(className);
            setFieldValueForcely(cls, obj, fieldName, value);
        } catch (ClassNotFoundException e) {
            logger.error(e);
            throw new WTException(e);
        }
    }

    /**
     * Set field value forcely
     *
     * @param clazz     the class instance
     * @param obj       object
     * @param fieldName field name
     * @param value     field value
     * @throws WTException Windchill exception
     */
    public static void setFieldValueForcely(Class clazz, Object obj, String fieldName, Object value) throws WTException {
        Field field = getField(clazz, fieldName);
        if (field == null) {
            NoSuchFieldException noSuchFieldException = new NoSuchFieldException("class: " + clazz
                    + "@@obj:" + obj + "@@field name: " + fieldName + "@@value: " + value);
            throw new WTException(noSuchFieldException);
        }
        System.setSecurityManager(null);
        field.setAccessible(true);
        System.setSecurityManager(DEFAULT_SECURITY_MANAGER);
        try {
            field.set(obj, value);
        } catch (IllegalAccessException e) {
            logger.error(e, e);
            throw new WTException(e, e.getLocalizedMessage());
        }
    }

    private static Field getField(Class clazz, String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            while (clazz.getSuperclass() != null) {
                clazz = clazz.getSuperclass();
                try {
                    field = clazz.getDeclaredField(fieldName);
                    if (field != null) {
                        break;
                    }
                } catch (NoSuchFieldException e1) {
                    logger.error(e1, e1);
                }
            }
        }
        return field;
    }

    /**
     * Judge if the method has return value
     *
     * @param methodName method name
     * @param cls        class
     * @param argTypes   arrays of arguments' types
     * @return has return value or not
     * @throws WTException Windchill exception
     */
    public static boolean hasReturnValue(String methodName, Class cls, Class[] argTypes) throws WTException {
        boolean hasReturnValue = true;
        try {
            Method method = cls.getMethod(methodName, argTypes);
            Class returnType = method.getReturnType();
            if (void.class.equals(returnType)) {
                hasReturnValue = false;
            }
        } catch (NoSuchMethodException e) {
            logger.error(e);
            throw new WTException(e);
        }
        return hasReturnValue;
    }

    /**
     * Initialilze the method server authentication
     *
     * @param user user
     * @param pwd  password
     * @throws WTException Windhcill exception
     */
    public static void initMethodServerAuth(String user, String pwd) throws WTException {
        RemoteMethodServer methodServer = RemoteMethodServer.getDefault();
        methodServer.setUserName(user);
        methodServer.setPassword(pwd);
    }

    /**
     * Private constructor
     */
    private ReflectionUtil() {
    }
}